#!/usr/bin/env node
/**
 * 绝区零材料图片爬虫 - Node.js版
 * 从B站游戏wiki爬取材料图片
 */

const axios = require('axios');
const cheerio = require('cheerio');
const fs = require('fs-extra');
const path = require('path');
const { URL } = require('url');

// 用于pkg打包的兼容性设置
if (typeof process.pkg !== 'undefined') {
    // 如果是pkg打包的环境，设置一些兼容性选项
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
}

class ZZZSpider {
    constructor() {
        this.baseUrl = 'https://wiki.biligame.com';
        this.targetUrl = 'https://wiki.biligame.com/zzz/材料筛选';
        this.saveDir = 'zzz_materials';
        this.downloadCount = 0;
        
        // 设置axios默认配置
        this.axios = axios.create({
            timeout: 30000,
            headers: {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
                'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
                'Accept-Encoding': 'gzip, deflate, br',
                'Connection': 'keep-alive',
                'Upgrade-Insecure-Requests': '1'
            }
        });
    }

    /**
     * 输出带时间戳的日志
     */
    log(message) {
        const timestamp = new Date().toLocaleTimeString('zh-CN');
        console.log(`[${timestamp}] ${message}`);
    }

    /**
     * 清理文件名，移除非法字符
     */
    cleanFilename(filename) {
        return filename
            .replace(/[<>:"/\\|?*]/g, '')
            .replace(/\s+/g, '_')
            .trim()
            .substring(0, 100) || '未知材料';
    }

    /**
     * 从URL获取文件扩展名
     */
    getFileExtension(url, contentType = '') {
        // 先从Content-Type判断
        if (contentType.includes('jpeg') || contentType.includes('jpg')) return '.jpg';
        if (contentType.includes('png')) return '.png';
        if (contentType.includes('gif')) return '.gif';
        if (contentType.includes('webp')) return '.webp';

        // 从URL判断
        try {
            const urlPath = new URL(url).pathname;
            const ext = path.extname(urlPath);
            return ext || '.png';
        } catch {
            return '.png';
        }
    }

    /**
     * 下载单个图片
     */
    async downloadImage(imgUrl, name) {
        try {
            // 处理相对URL
            if (imgUrl.startsWith('//')) {
                imgUrl = 'https:' + imgUrl;
            } else if (imgUrl.startsWith('/')) {
                imgUrl = this.baseUrl + imgUrl;
            }

            this.log(`正在下载: ${name}`);
            this.log(`图片URL: ${imgUrl}`);

            // 下载图片
            const response = await this.axios.get(imgUrl, {
                responseType: 'arraybuffer',
                headers: {
                    'Referer': this.baseUrl + '/',
                    'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8'
                }
            });

            // 检查文件大小
            if (response.data.length < 1000) {
                this.log(`✗ 文件过小，可能是错误页面: ${name}`);
                return false;
            }

            // 确定文件扩展名
            const contentType = response.headers['content-type'] || '';
            const ext = this.getFileExtension(imgUrl, contentType);

            // 生成文件名
            const cleanName = this.cleanFilename(name);
            let filename = cleanName + ext;
            let filepath = path.join(this.saveDir, filename);

            // 处理重复文件名
            let counter = 1;
            while (await fs.pathExists(filepath)) {
                filename = `${cleanName}_${counter}${ext}`;
                filepath = path.join(this.saveDir, filename);
                counter++;
            }

            // 保存文件
            await fs.writeFile(filepath, response.data);

            this.downloadCount++;
            this.log(`✓ 已保存: ${filename} (${response.data.length} 字节)`);
            return true;

        } catch (error) {
            this.log(`✗ 下载失败 ${name}: ${error.message}`);
            return false;
        }
    }

    /**
     * 解析材料页面
     */
    async parseMaterials() {
        try {
            this.log('正在访问材料筛选页面...');

            const response = await this.axios.get(this.targetUrl);
            this.log('✓ 页面获取成功');

            // 使用cheerio解析HTML
            const $ = cheerio.load(response.data);

            // 查找目标表格
            const table = $('#CardSelectTr');
            if (table.length === 0) {
                this.log('✗ 未找到ID为"CardSelectTr"的表格');
                return [];
            }

            this.log('✓ 找到目标表格');

            const materials = [];
            const rows = table.find('tr');

            rows.each((index, row) => {
                const $row = $(row);
                const cells = $row.find('td');

                if (cells.length >= 2) {
                    // 获取第一列的图片
                    const $firstCell = $(cells[0]);
                    const $img = $firstCell.find('img');

                    if ($img.length > 0) {
                        const imgUrl = $img.attr('src');

                        // 获取第二列的名称
                        const $secondCell = $(cells[1]);
                        let name = $secondCell.text().trim();

                        // 如果名称为空，尝试从链接获取
                        if (!name) {
                            const $link = $secondCell.find('a');
                            name = $link.attr('title') || $link.text().trim();
                        }

                        if (name && imgUrl && name.length > 1 && name.length < 50) {
                            materials.push({
                                name: name,
                                imgUrl: imgUrl,
                                rowIndex: index
                            });
                            this.log(`发现材料: ${name}`);
                        }
                    }
                }
            });

            return materials;

        } catch (error) {
            this.log(`✗ 访问页面失败: ${error.message}`);
            return [];
        }
    }

    /**
     * 添加下载延迟
     */
    async delay(ms = 500) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    /**
     * 主下载流程
     */
    async start() {
        console.log('=====================================================');
        console.log('           绝区零材料图片爬虫 - Node.js版');
        console.log('=====================================================');
        console.log('');

        try {
            // 创建保存目录
            await fs.ensureDir(this.saveDir);
            this.log(`✓ 创建保存目录: ${this.saveDir}`);

            // 解析材料信息
            const materials = await this.parseMaterials();

            if (materials.length === 0) {
                this.log('✗ 未找到任何材料数据');
                return;
            }

            this.log(`共找到 ${materials.length} 个材料，开始下载...`);
            console.log('');

            // 下载图片
            let successCount = 0;
            for (let i = 0; i < materials.length; i++) {
                const material = materials[i];
                console.log(`[${i + 1}/${materials.length}]`);

                if (await this.downloadImage(material.imgUrl, material.name)) {
                    successCount++;
                }

                // 添加延迟避免请求过于频繁
                if (i < materials.length - 1) {
                    await this.delay(500);
                }
            }

            console.log('');
            console.log('='.repeat(50));
            this.log(`下载完成！成功: ${successCount}/${materials.length}`);
            this.log(`图片保存在: ${path.resolve(this.saveDir)}`);
            console.log('='.repeat(50));

        } catch (error) {
            this.log(`✗ 程序运行错误: ${error.message}`);
        }
    }
}

/**
 * 主函数
 */
async function main() {
    const spider = new ZZZSpider();
    
    try {
        await spider.start();
    } catch (error) {
        console.error('程序异常:', error.message);
    }
    
    console.log('');
    console.log('按任意键退出...');
    
    // 等待用户输入
    process.stdin.setRawMode(true);
    process.stdin.resume();
    process.stdin.on('data', () => {
        process.exit(0);
    });
}

// 如果直接运行此文件
if (require.main === module) {
    main();
}

module.exports = ZZZSpider;
